#ifndef __SELECT_SERVER_HPP__
#define __SELECT_SERVER_HPP__

#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <sys/select.h>
#include "Sock.hpp"

using namespace NS_sock;

#define BITS 8
#define NUM (sizeof(fd_set)*BITS)
#define FD_NONE -1

class SelectServer
{
private:
    int _listenSock;
    uint16_t _port;
    int _fdArray[NUM];

public:

    SelectServer(const uint16_t& port)
        :_port(port)
    {
        // 获取流式套接字
        _listenSock=Sock::Socket();
        // 绑定
        Sock::Bind(_listenSock,_port);
        // 设置监听状态
        Sock::Listen(_listenSock);

        for(int i=0;i<NUM;++i)
        {
            _fdArray[i]=FD_NONE;
        }
        // 规定_listenSock在_fdArray数组0号下标
        _fdArray[0]=_listenSock;
    }

    ~SelectServer()
    {
        if(_listenSock>=0)
            close(_listenSock);
    }

    void Start()
    {
        // fd_set rfds; // fd_set: 文件描述符集
        /*
            void FD_CLR(int fd, fd_set *set);
            int  FD_ISSET(int fd, fd_set *set);
            void FD_SET(int fd, fd_set *set);
            void FD_ZERO(fd_set *set);
        */
        // FD_ZERO(&rfds);

        while(true)
        {
            // struct timeval timeout={5,0};
            // int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
            // nfds: 最大文件描述符+1
            // fd_set *readfds, fd_set *writefds,fd_set *exceptfds: 输入输出型参数,传入需要等待的文件描述符,
            //                                                      传出表示哪些文件描述符就绪
            // struct timeval *timeout: 1,传入nullptr表示阻塞等待 2,传入{0,0}表示非阻塞等待 
            //                          3,传入{!0,!0}表示按照时间等待,如果在等待期间资源就绪,则返回剩余时间
            
            fd_set rfds; // fd_set: 文件描述符集
            FD_ZERO(&rfds);

            int maxfd=_listenSock;

            for(int i=0;i<NUM;++i)
            {
                if(_fdArray[i]==FD_NONE) continue; // 如果当前位置没有有效文件描述符,则跳过

                FD_SET(_fdArray[i],&rfds); // 标识当前位置的文件描述符需要等待
                if(maxfd<_fdArray[i]) maxfd=_fdArray[i]; // 更新最大文件描述符
            }

            // int n=select(_listenSock+1,&rfds,nullptr,nullptr,&timeout);
            int n=select(maxfd+1,&rfds,nullptr,nullptr,nullptr);

            // 使用select进行等待listenSock和acceptSock需要解决以下问题:
            // nfds: acceptSock有多个,在服务器运行区间伴随着acceptSock的获取与释放,所以需要动态获取最大maxfd
            // readfds,writefds,exceptfds: 输入输出型参数,也即输入与输出会不一致,所以注定了我们每一次select都要对参数进行重新添加
            // 所以我们必须自己将合法的文件描述符需要单独全部保存起来 用来支持：1. 更新最大fd 2.更新位图结构

            switch(n)
            {
                case 0:
                    std::cout<<"没有文件描述符就绪!"<<std::endl;
                    break;
                case -1:
                    std::cerr<<"select error: "<<"errno: "<<errno<<" : "<<strerror(errno)<<std::endl;
                    break;
                default:
                    std::cout<<"有文件描述符就绪"<<std::endl;
                    Process(rfds);
                    break;
            }
        }
    }

    void Accepter()
    {
        std::string clientip;
        uint16_t clientport;  
        // 无需等待,直接获取
        int sockfd=Sock::Accept(_listenSock,&clientport,&clientip);
        printf("get new list: [%s %d] sockfd: %d\n",clientip.c_str(),clientport,sockfd);

        // 往_fdArray数组中添加新增的acceptSock
        int i=0;
        for(;i<NUM;++i)
        {
            if(_fdArray[i]==FD_NONE)
            {
                break;
            }
        }

        if(i>=NUM) // _fdArray满了,不能进行添加acceptSock
        {
            std::cout<<"acceptSock: "<<sockfd<<" _fdArray fill, add fail"<<std::endl;
            close(sockfd);
        }
        else // _fdArray没有满,可以添加
        {
            _fdArray[i]=sockfd;
        }
    }

    void Recver(int pos)
    {
        char buffer[1024];
        
        int n=recv(_fdArray[pos],buffer,sizeof(buffer)-1,0);
        if(n>0)
        {
            buffer[n]=0;
            std::cout<<"recv sockfd: "<<_fdArray[pos]<<" # "<<buffer<<std::endl;
        }
        else if(0==n)
        {
            // 写端关闭,则读端也关闭
            close(_fdArray[pos]);
            // 相应位置置为原状态
            _fdArray[pos]=FD_NONE;
        }
        else
        {
            std::cerr<<"recv error: "<<errno<<" - "<<strerror(errno)<<std::endl;
            close(_fdArray[pos]);
        }
    }

    void Process(const fd_set &fds)
    {
        for(int i=0;i<NUM;++i)
        {
            // 过滤掉不存在的fd
            if(_fdArray[i]==FD_NONE) continue;

            // 只处理就绪的fd
            if(FD_ISSET(_fdArray[i],&fds))
            {
                if(_fdArray[i]==_listenSock && 0==i)
                {
                    // listenSock
                    Accepter();
                }
                else
                {   
                    // acceptSock
                    Recver(i);
                }
            }
        }
    }

};

#endif